/*
	VTIMER.C - May 6 1995 - J.P.M. Mikkers

	This module implements a timer multiplexer, allowing multiple timers
	to be installed using only one hardware timer.

	Special features:

	- ability to call the system timer interrupt at the correct rate.
	- ability to install and use screen synchronized handlers.

   Added or changed by AlphaHelix:
   - ability to disable interrupt without messing up with vsync
   - readtimer2() corrected.
   - reset timer0 to proper mode upon exit (VT_Exit)
*/

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include "types.h"
#include "vtimer.h"

#define inportb(x) inp(x)
#define outportb(x,y) outp(x,y)
#define inport(x) inpw(x)
#define outport(x,y) outpw(x,y)
#define disable() _disable()
#define enable() _enable()


#define MAXTIMERS       2
#define RESERVEDLINES   10
#define DELAY           400


typedef struct VTIMER{
   int   active;
   long  speed;
   long  counter;
	void  (*handler)(void);
}VTIMER;


static  long    lspeed;

// Vertical blank interrupt variables:
static long vt_vcounter,vt_vspeed;
static void (*vt_v0handler)(void);
static void (*vt_v1handler)(void);


// Old system timer interrupt variables:

static long    vt_ocounter,vt_ospeed;
static void (interrupt far *vt_oldhandler)(void);


// User timers:

static  uchar   vt_flags;
static  VTIMER  vt_timer[MAXTIMERS];
static  int     vt_initialized=0;


static void starttimer1(ushort speed)
{
	outportb(0x43,0x30);
	outportb(0x40,speed&0xff);
	outportb(0x40,speed>>8);
}


static void starttimer2(void)
{
	outportb(0x61,inportb(0x61) | 0x01);
	outportb(0x43,0xb4);
	outportb(0x42,0x00);
	outportb(0x42,0x00);
}


static ushort readtimer1(void)
{
   ushort n;
	outportb(0x43,0);
	n = inportb(0x40);
	n += inportb(0x40) << 8;
	return 0x10000L - n;
}


static ushort readtimer2(void)
{
	int n;
   outportb(0x43,0x84);    // old: outport(0x43, 0xb0);
	n = inportb(0x42);
	n += inportb(0x42) << 8;
	return 0x10000L - n;
}


/*
static void WaitVR(void)
{
	while (inp(0x3da) & 8) ;
	while (!(inp(0x3da) & 8)) ;
}
*/

static ushort countlines(void)
{
   ushort lines=0;
   uchar state;

	while(!(inportb(0x3da)&8)){
		state=inportb(0x3da)&9;
		while((inportb(0x3da)&9)==state);
		lines++;
	}
	return lines;
}

static void dummyhandler(void)
{
}


static void interrupt timerint(void)
{
   int t;
   ushort adjust;
   ushort xspeed;
   long   sspeed=0x10000;
   uchar  flags[MAXTIMERS];
   int    oflag=0;


   xspeed = readtimer1();
   lspeed += xspeed;

	// Process old system timer?

	if(vt_flags&VT_CALLOLD){

		// Yes, check if it has to be called:

		vt_ocounter-=lspeed;

		if(vt_ocounter<=0){

			// counter underflow, so set flag to call system timer later:

			vt_ocounter+=vt_ospeed;
         oflag=1;
		}
		sspeed=vt_ocounter;
	}


	// Process vertical retrace interrupt?

	if(vt_flags&VT_VRSYNC){

		// yes, check if vertical retrace is coming soon:

		vt_vcounter-=lspeed;

		if(vt_vcounter<=0){

			/* counter underflow, so start waiting for the vertical retrace,
            but first reset the timer so we can check how long the wait
			   and vhandlers take */

         starttimer1(0);
			vt_vcounter+=vt_vspeed;
			vt_v0handler();
         if (xspeed > DELAY)
            adjust = RESERVEDLINES;
         else
            adjust = countlines();
			vt_v1handler();
         if(adjust>RESERVEDLINES) vt_vspeed+=10;
         if(adjust<RESERVEDLINES) vt_vspeed-=10;
         lspeed += readtimer1();                   // adjust lspeed
		}
		sspeed=min(sspeed,vt_vcounter);
	}

	// process all timers:

	for(t=0;t<MAXTIMERS;t++){

      flags[t]=0;

		// this timer is being used?

		if(vt_timer[t].active){

			// update counter

			vt_timer[t].counter-=lspeed;

			// underflow ? -> reset counter and set flag to call the handler

			if(vt_timer[t].counter<=0){
				vt_timer[t].counter+=vt_timer[t].speed;
            flags[t]=1;
			}

			sspeed=min(vt_timer[t].counter,sspeed);
		}
	}

	lspeed=sspeed;

	starttimer1(lspeed);
	enable();

	if(oflag){
		oflag=0;
      vt_oldhandler();
	}
	else
		outportb(0x20,0x20);

   for(t=0;t<MAXTIMERS;t++){
      if(flags[t]){
         flags[t] = 0;
			vt_timer[t].handler();
		}
	}
}


void VT_Exit(void)
{
   if(--vt_initialized == 0) {
		disable();
      outportb(0x43,0x36);
		outportb(0x40,0);
		outportb(0x40,0);
		_dos_setvect(0x8,vt_oldhandler);
		enable();
   }
}



static ushort vbspeed(void)
{
   ushort s1;

	disable();
	while(inportb(0x3da)&8);
	while(!(inportb(0x3da)&8));
   starttimer2();
	while(inportb(0x3da)&8);
	while(!(inportb(0x3da)&8));
   s1=readtimer2();
	enable();
	return s1;
}


void VT_Init(void)
{
	int t;

	if(!vt_initialized){

		for(t=0;t<MAXTIMERS;t++){
			vt_timer[t].active=0;
			vt_timer[t].handler=NULL;
		}

		vt_oldhandler=_dos_getvect(0x8);
		vt_v0handler=dummyhandler;
		vt_v1handler=dummyhandler;
		vt_flags=VT_CALLOLD;
		vt_ospeed=0x10000;
		vt_ocounter=0x10000;
		vt_vspeed=vbspeed()-1024;       // 1193181L/76;
		vt_vcounter=vt_vspeed;
		lspeed=0x10000;
		_dos_setvect(0x8,timerint);
		starttimer1(lspeed);
      vt_initialized++;
	}
}


short VT_Alloc(void)
{
   short t;
	for(t=0;t<MAXTIMERS;t++){
		if(vt_timer[t].handler==NULL){
			vt_timer[t].active=0;
			vt_timer[t].speed=0x10000;
			vt_timer[t].handler=dummyhandler;
			return t;
		}
	}
	return -1;
}


void VT_Free(short handle)
{
	vt_timer[handle].active=0;
	vt_timer[handle].handler=NULL;
	vt_timer[handle].speed=0;
}


void VT_SetHandler(short handle,void (*handler)(void))
{
	vt_timer[handle].handler=handler;
}


void VT_SetSpeed(short handle,long speed)
{
	if(!vt_timer[handle].active){
		vt_timer[handle].counter=speed;
	}
	vt_timer[handle].speed=speed;
}


void VT_SetBPM(short handle,uchar bpm)
{
	VT_SetSpeed(handle,(125*1193181L)/(50L*bpm));
}


void VT_Start(short handle)
{
	vt_timer[handle].active=1;
}


void VT_Stop(short handle)
{
	vt_timer[handle].active=0;
}


void VT_Mode(uchar flags)
{
	vt_flags=flags;
}


void VT_V0Handler(void (*handler)(void))
{
	vt_v0handler=handler;
}


void VT_V1Handler(void (*handler)(void))
{
	vt_v1handler=handler;
}


